SAN - Praktische Arbeit (Marvel Universe Social Network)¶

Netzwerkanalyse¶

In [ ]:
import os
import sys
import numpy as np
import random
import itertools
import pandas as pd
import seaborn as sns
import networkx as nx
from networkx.algorithms import bipartite, centrality, community
from sklearn.metrics import pairwise_distances
from scipy.spatial.distance import jaccard
import matplotlib.pyplot as plt
from itertools import combinations
import community as community_louvain
from scipy.spatial.distance import pdist, squareform
from scipy import sparse
import re

from helper import *

Daten einlesen¶

In [ ]:
nodes_df = pd.read_csv('Daten/nodes.csv')
edges_df = pd.read_csv('Daten/edges.csv')
hero_network = pd.read_csv('Daten/hero-network.csv')

Initiales Netzwerk¶

Hier erstellen wir ein ungerichtetes, bipartites Netzwerk mit allen Verbindungen, ohne Filterungen, wobei Comics und Charaktere als Knoten fungieren.

In [ ]:
B = nx.Graph()

heroes = nodes_df[nodes_df['type'] == 'hero']['node']
comics = nodes_df[nodes_df['type'] == 'comic']['node']

B.add_nodes_from(heroes, bipartite=0)  # label heroes as 0
B.add_nodes_from(comics, bipartite=1)  # label comics as 1
B.name = "Marvel Universe"

# Add edges based on the edges_df data
# Each row in edges_df is an edge between a hero and a comic
for _, row in edges_df.iterrows():
    B.add_edge(row['hero'], row['comic'])

print_network_information(B)
Information for given Graph with name 'Marvel Universe':
	Graph is directed: False
	Number of nodes: 19091
	Number of edges: 96104

Eine Gruppe von Knoten repräsentiert Helden, die andere Gruppe repräsentiert Comics. Eine Kante wird zwischen einem Helden und einem Comic hinzugefügt, wenn der Held in diesem Comic vorkommt.

Da der Datensatz sehr gross ist, werden wir nun ein Sample des Netzwerks erstellen, um die Struktur des Netzwerks übersichtlich zu visualisieren. Hierzu wenden wir Random Walk Sampling an: Wir beginnen bei einem zufälligen Knoten (entweder ein Held oder ein Comic) und führen einen Random Walk durch, um verbundene Knoten zu sammeln. Dadurch können wir sicherstellen, dass die ausgewählten Helden und Comics tatsächlich miteinander verbunden sind.

In [ ]:
# Randomly select a starting node (either hero or comic)
starting_node = random.choice(list(B.nodes()))

# Perform a random walk to get a sample of the network
sampled_nodes = perform_random_walk(B, starting_node, num_steps=50)

# Create a subgraph with the sampled nodes
sampled_graph = B.subgraph(sampled_nodes)

sampled_heroes_nodes = [node for node in heroes if node in sampled_graph.nodes]
sampled_comics_nodes = [node for node in comics if node in sampled_graph.nodes]

# Draw the sampled graph
plt.figure(figsize=(20, 10))
plt.title("Sampled Two Mode Network Graph", fontsize=16)
draw_graph_and_color_groups(sampled_graph, groups=[sampled_heroes_nodes, sampled_comics_nodes], layout="spring")
plt.show()

print_network_information(sampled_graph)
Information for given Graph with name 'Marvel Universe':
	Graph is directed: False
	Number of nodes: 46
	Number of edges: 63

Das Diagramm zeigt die Stichprobe des Marvel Universe Netzwerks, das aus einem Random Walk mit 50 Schritten resultiert, wobei blaue Knoten Helden repräsentieren, grüne Knoten Comics repräsentieren und die Linien die Verbindungen zwischen ihnen darstellen.

In diesem Netzwerk gibt es keine direkten Verbindungen zwischen Helden oder zwischen Comics untereinander. Stattdessen sind die Helden mit Comics verbunden, was anzeigt, in welchen Comics die jeweiligen Helden erscheinen. Diese Struktur ist typisch für bipartite Netzwerke, in denen die beiden Knotentypen unterschiedliche Entitäten darstellen (in diesem Fall Helden und Comics) und die Kanten die Beziehungen zwischen diesen Entitäten repräsentieren.

One-Mode-Netzwerk¶

Da das Netzwerk sehr gross ist, würden die Berechnungen und Darstellungen sehr lange dauern. Deshalb haben wir uns entschieden, das Netzwerk in ein One-Mode-Netzwerk umzuwandeln, in dem die Helden direkt miteinander verbunden sind, wenn sie in mindestens einem Comic gemeinsam auftreten.

In [ ]:
# Project the bipartite graph onto heroes nodes using the nodes DataFrame
heroes_nodes = nodes_df[nodes_df['type'] == 'hero']['node'].tolist()

# Project the bipartite graph onto heroes nodes
G_one_mode = bipartite.projected_graph(B, heroes_nodes)

print_network_information(G_one_mode)
Information for given Graph with name 'Marvel Universe':
	Graph is directed: False
	Number of nodes: 6440
	Number of edges: 171644

Das One-Mode Netzwerk besteht nun aus 6'440 Knoten, die jeweils einen Helden darstellen. Es gibt 171'644 Kanten im Graphen, wobei jede Kante einen gemeinsamen Comic-Auftritt zwischen zwei Helden anzeigt.

Hier zeigen wir ein kleines Sample des One-Mode-Netzwerks, das aus einem Random Walk mit 25 Schritten resultiert, wobei blaue Knoten Helden repräsentieren und die Linien die Verbindungen zwischen ihnen darstellen.

In [ ]:
# sample G_one_mode using random walk
starting_node = random.choice(list(G_one_mode.nodes()))
sampled_nodes = perform_random_walk(G_one_mode, starting_node, num_steps=25)
sampled_graph = G_one_mode.subgraph(sampled_nodes)

# Draw the sampled graph
plt.figure(figsize=(20, 10))
plt.title("Sampled One Mode Network Graph", fontsize=16)
draw_graph_and_color_groups(sampled_graph, groups=[sampled_nodes], layout="spring")
plt.show()

Dazu werden wir ein Subgraph erstellen, welche nur die 100 am häufigsten vorkommenden Helden berücksichtigt, um einige Berechnungen zu beschleunigen.

In [ ]:
top_heroes = edges_df['hero'].value_counts().head(100).index.tolist()
top_heroes[:5]
Out[ ]:
['SPIDER-MAN/PETER PARKER',
 'CAPTAIN AMERICA',
 'IRON MAN/TONY STARK',
 'THING/BENJAMIN J. GR',
 'THOR/DR. DONALD BLAK']
In [ ]:
# Filter the one-mode network to only include the top 100 heroes
top_heroes_subgraph = G_one_mode.subgraph(top_heroes)

# Draw the projected graph
plt.figure(figsize=(20, 10))
plt.title("One-mode projection of the 100 most popular heroes in the Marvel Universe", fontsize=16)
draw_graph_and_color_groups(top_heroes_subgraph, layout="spring")
plt.show()

print_network_information(top_heroes_subgraph)
Information for given Graph with name 'Marvel Universe':
	Graph is directed: False
	Number of nodes: 100
	Number of edges: 4076

Wir sehen, dass die 100 am häufigsten vorkommenden Helden sehr stark vernetzt sind. Es gibt viele sehr zentrale Helden. Sie könnten als Schlüsselfiguren oder Hauptcharaktere im Marvel-Universum betrachtet werden.

Da wir nun unsere verschiedenen Netzwerke erstellt haben, können wir mit der Analyse beginnen.

Frage 1 - Welche Helden sind die zentralsten im Netzwerk?¶

Um die Frage zu beantworten, welche Helden die zentralsten im Netzwerk sind, werden wir die drei genannten Zentralitätsmasse verwenden: Degree-Centrality, Betweenness-Centrality und Closeness-Centrality. Diese Masse geben jeweils unterschiedliche Aspekte der Zentralität eines Knotens im Netzwerk an:

  • Degree-Centrality misst die Anzahl der direkten Verbindungen, die ein Knoten hat. Für Helden in unserem Netzwerk bedeutet ein hoher Wert, dass sie in vielen verschiedenen Comics erscheinen.

  • Betweenness-Centrality gibt an, wie oft ein Knoten auf den kürzesten Pfaden zwischen anderen Knoten liegt. Ein hoher Wert deutet darauf hin, dass der Held eine wichtige Rolle in der Verbindung verschiedener Teile des Netzwerks spielt.

  • Closeness-Centrality misst, wie nahe ein Knoten im Durchschnitt zu allen anderen Knoten im Netzwerk liegt. Ein hoher Wert zeigt an, dass ein Held im Durchschnitt schneller mit anderen Knoten verbunden werden kann.

Ganzes Netzwerk¶

Beachte: Die Laufzeit der Betweenness und Closeness Zentralitäten dauern jeweils 3.5 und 1 Minute.

In [ ]:
# Degree
degree_centrality = nx.degree_centrality(G_one_mode)
top_degree_centrality = sorted(degree_centrality.items(), key=lambda x: x[1], reverse=True)
print_centrality(top_degree_centrality, "Helden mit der höchsten Degree-Zentralität")
Helden mit der höchsten Degree-Zentralität:
  CAPTAIN AMERICA: 0.2980
  SPIDER-MAN/PETER PARKER: 0.2724
  IRON MAN/TONY STARK: 0.2432
  THING/BENJAMIN J. GR: 0.2249
  MR. FANTASTIC/REED R: 0.2199

In [ ]:
# Betweenness
betweenness_centrality = nx.betweenness_centrality(G_one_mode)
top_betweenness_centrality = sorted(betweenness_centrality.items(), key=lambda x: x[1], reverse=True)
print_centrality(top_betweenness_centrality, "Helden mit der höchsten Betweenness-Zentralität")
Helden mit der höchsten Betweenness-Zentralität:
  SPIDER-MAN/PETER PARKER: 0.0712
  CAPTAIN AMERICA: 0.0553
  IRON MAN/TONY STARK: 0.0373
  WOLVERINE/LOGAN: 0.0348
  HAVOK/ALEX SUMMERS: 0.0348

In [ ]:
# Closeness
closeness_centrality = nx.closeness_centrality(G_one_mode)
top_closeness_centrality = sorted(closeness_centrality.items(), key=lambda x: x[1], reverse=True)
print_centrality(top_closeness_centrality, "Helden mit der höchsten Closeness-Zentralität")
Helden mit der höchsten Closeness-Zentralität:
  CAPTAIN AMERICA: 0.5827
  SPIDER-MAN/PETER PARKER: 0.5734
  IRON MAN/TONY STARK: 0.5620
  THING/BENJAMIN J. GR: 0.5581
  MR. FANTASTIC/REED R: 0.5563

Helden mit der höchsten Degree-Zentralität, wie Captain America und Spider-Man, sind also diejenigen, die mit den meisten anderen Helden direkt verbunden sind. Sie sind möglicherweise die beliebtesten und aktivsten Charaktere, die in einer grossen Anzahl von Comics mit verschiedenen anderen Helden erscheinen.

Spider-Man hat hier die höchste Betweenness-Zentralität, was darauf hindeutet, dass er eine wichtige "Brückenfigur" im Marvel-Universum ist, die verschiedene Gruppen oder Gemeinschaften von Helden miteinander verbindet. Er könnte in vielen Storylines eine zentrale Rolle spielen, die unterschiedliche Helden zusammenführt.

Captain America und Spider-Man haben auch hier die höchsten Werte, was bedeutet, dass sie im Durchschnitt die kürzesten Pfade zu allen anderen Helden im Netzwerk haben. Dies deutet auf ihre zentrale Stellung in der "sozialen Struktur" des Marvel-Universums hin.

Top 100 Helden Netzwerk¶

In [ ]:
# Degree
degree_centrality_top_100 = nx.degree_centrality(top_heroes_subgraph)
top_degree_centrality_top_100 = sorted(degree_centrality_top_100.items(), key=lambda x: x[1], reverse=True)
print_centrality(top_degree_centrality_top_100, "Helden mit der höchsten Degree-Zentralität")
Helden mit der höchsten Degree-Zentralität:
  CYCLOPS/SCOTT SUMMER: 1.0000
  WOLVERINE/LOGAN: 1.0000
  CAPTAIN AMERICA: 1.0000
  HUMAN TORCH/JOHNNY S: 1.0000
  MR. FANTASTIC/REED R: 1.0000

In [ ]:
# Betweenness
betweenness_centrality_top_100 = nx.betweenness_centrality(top_heroes_subgraph)
top_betweenness_centrality_top_100 = sorted(betweenness_centrality_top_100.items(), key=lambda x: x[1], reverse=True)
print_centrality(top_betweenness_centrality_top_100, "Helden mit der höchsten Betweenness-Zentralität")
Helden mit der höchsten Betweenness-Zentralität:
  CYCLOPS/SCOTT SUMMER: 0.0037
  WOLVERINE/LOGAN: 0.0037
  CAPTAIN AMERICA: 0.0037
  HUMAN TORCH/JOHNNY S: 0.0037
  MR. FANTASTIC/REED R: 0.0037

In [ ]:
# Closeness
closeness_centrality_top_100 = nx.closeness_centrality(top_heroes_subgraph)
top_closeness_centrality_top_100 = sorted(closeness_centrality_top_100.items(), key=lambda x: x[1], reverse=True)
print_centrality(top_closeness_centrality_top_100, "Helden mit der höchsten Closeness-Zentralität")
Helden mit der höchsten Closeness-Zentralität:
  CYCLOPS/SCOTT SUMMER: 1.0000
  WOLVERINE/LOGAN: 1.0000
  CAPTAIN AMERICA: 1.0000
  HUMAN TORCH/JOHNNY S: 1.0000
  MR. FANTASTIC/REED R: 1.0000

Jeder dieser Helden hat eine Degree-Zentralität und Closeness-Zentralität von 1, was bedeutet, dass sie alle mit jedem anderen Helden im Netzwerk direkt verbunden sind. Die Betweenness-Zentralität ist für alle fünf Helden identisch und ziemlich niedrig, was wiederum die hohe Dichte des Netzwerks und die geringe Notwendigkeit von "Brücken"-Helden in diesem Netzwerk anzeigt.

Da wir jetzt wissen, welche Helden die zentralsten sind, wollen wir weitere Analysen auf diesen Hauptcharakteren durchführen.

Verbindung zwischen Captain America, Iron Man und Spiderman¶

Wir haben herausgefunden, dass Captain America, Ironman und Spider-Man im hero-network.csv die meisten Verbindungen haben. Wir wollen nun herausfinden, wie die anderen Helden mit unseren "Haupt-Helden" verbunden sind und welche Helden nur auf einen, mit zwei oder drei "Haupt-Helden" verbunden sind.

In [ ]:
# connection between captain america,ironman and spiderman
captAmerica = Subset = hero_network[hero_network['hero1'] == 'CAPTAIN AMERICA']
ironMan = hero_network[hero_network['hero1'].str.contains('IRON MAN/TONY STARK')]
spiderMan = hero_network[hero_network['hero1'].str.contains('SPIDER-MAN/PETER PAR')]
Subset = pd.concat([captAmerica, ironMan, spiderMan], axis=0)

G = nx.from_pandas_edgelist(Subset, 'hero1', 'hero2')

node_colors = []

for node in G.nodes():
    if 'SPIDER-MAN/PETER PAR' in node:
        node_colors.append('red')  # Spider-Man in Rot
    elif 'IRON MAN/TONY STARK' in node:
        node_colors.append('gold')  # Iron Man in Gold
    elif 'CAPTAIN AMERICA' in node:
        node_colors.append('blue')  # Captain America in Blau
    else:
        node_colors.append('grey')  # Alle anderen Knoten in Grau
        
plt.figure(figsize=(20, 15))
nx.draw(G, node_color=node_colors, with_labels=True, node_size=50, font_size=8)

# Create a legend
red_patch = plt.Line2D([0], [0], marker='o', color='w', label='Spider-Man', markersize=10, markerfacecolor='red')
gold_patch = plt.Line2D([0], [0], marker='o', color='w', label='Iron Man', markersize=10, markerfacecolor='gold')
blue_patch = plt.Line2D([0], [0], marker='o', color='w', label='Captain America', markersize=10, markerfacecolor='blue')
plt.legend(handles=[red_patch, gold_patch, blue_patch])

plt.show()

Wir sehen, dass Captain America und Spider Man sehr viele einzelne Verbindungen zu Helden haben. Es gibt auch sehr viele Helden, welche eine Verbingung zu allen 3 "Haupt-Helden" haben. Dies sieht man anhand des grossen Clusters in der Mitte des Netzwerkes. Zudem ist gut zu erkennen, dass es es sehr wenige Helden gibt, die nur zu Iron Man und Spider Man verbunden sind (Also kein Captain America). Viel mehr sind zusammen mit Spider Man und Captain America oder Iron Man zu Captain America verbunden.

Diese Visualisierung dient zur Veranschaulichung der Wichtigkeit der "Haupt-Helden". Es zeigt, dass die Haupt-Helden sehr viele Verbindungen zu anderen Helden haben und somit eine wichtige Rolle im Marvel-Universum spielen.

Um das angewendete Prizip ein bisschen besser anzusehen, visualisieren wir im folgenden Plot noch eine vereinfachte Version des Netzwerkes mit 25 samples pro "Haupt-Held".

In [ ]:
# connection between captain america,ironman and spiderman
captAmerica_sample = Subset_sample = hero_network[hero_network['hero1'] == 'CAPTAIN AMERICA'].sample(25)
ironMan_sample = hero_network[hero_network['hero1'].str.contains('IRON MAN/TONY STARK')].sample(25)
spiderMan_sample = hero_network[hero_network['hero1'].str.contains('SPIDER-MAN/PETER PAR')].sample(25)
Subset_sample = pd.concat([captAmerica_sample, ironMan_sample, spiderMan_sample], axis=0)

G = nx.from_pandas_edgelist(Subset_sample, 'hero1', 'hero2')

plt.figure(figsize=(20, 10))
nx.draw(G, with_labels=True, node_size=8)
plt.show()

Die vereinfachte Ansicht, gibt uns eine gute Übersicht. Je nach seed sehen wir, wie auch schon in der gesamt Ansicht, dass Captain America mehrmals vorkommen muss. Dieses haben wir bereits in der Explorativen Datenanalyse gemacht.

Uns interessiert aber vorerst die genaue Anzahl der Verbindungen. Dazu erstellen wir eine Tabelle mit den Anzahl Verbindungen zu den "Haupt-Helden". Die "mehrfachen Captain America's" analysieren wir danach.

Anzahl Verbindungen zu den "Haupt-Helden"¶

In [ ]:
# To create the table as requested, we need to count the connections based on the criteria given.
# We will assume that the hero_network dataframe has already been loaded as per previous code snippets.

# Counting connections for each main hero
capt_america_count = captAmerica['hero2'].nunique()
iron_man_count = ironMan['hero2'].nunique()
spiderman_count = spiderMan['hero2'].nunique()

# Counting mutual connections
capt_america_iron_man = hero_network[(hero_network['hero1'] == 'CAPTAIN AMERICA') & 
                                      hero_network['hero2'].str.contains('IRON MAN/TONY STARK')].shape[0]
capt_america_spiderman = hero_network[(hero_network['hero1'] == 'CAPTAIN AMERICA') & 
                                       hero_network['hero2'].str.contains('SPIDER-MAN/PETER PAR')].shape[0]
iron_man_spiderman = hero_network[(hero_network['hero1'].str.contains('IRON MAN/TONY STARK')) & 
                                  hero_network['hero2'].str.contains('SPIDER-MAN/PETER PAR')].shape[0]

# Counting connections between all three
all_three_connections = hero_network[(hero_network['hero1'].str.contains('CAPTAIN AMERICA') & 
                                      hero_network['hero2'].str.contains('IRON MAN/TONY STARK')) |
                                     (hero_network['hero1'].str.contains('IRON MAN/TONY STARK') & 
                                      hero_network['hero2'].str.contains('SPIDER-MAN/PETER PAR')) |
                                     (hero_network['hero1'].str.contains('CAPTAIN AMERICA') & 
                                      hero_network['hero2'].str.contains('SPIDER-MAN/PETER PAR'))].shape[0]

# Creating the table
table_data = {
    'Connection': ['Captain America', 'Captain America - Spiderman', 'Captain America - Iron Man',
                   'Iron Man', 'Iron Man - Spiderman', 'Spiderman', 
                   'Captain America - Iron Man - Spiderman'],
    'Number of Connections': [capt_america_count, capt_america_spiderman, capt_america_iron_man,
                              iron_man_count, iron_man_spiderman, spiderman_count,
                              all_three_connections]
}

connection_table = pd.DataFrame(table_data)

connection_table.sort_values(by='Number of Connections', ascending=False)
Out[ ]:
Connection Number of Connections
0 Captain America 1426
5 Spiderman 1286
3 Iron Man 1132
6 Captain America - Iron Man - Spiderman 353
2 Captain America - Iron Man 220
1 Captain America - Spiderman 79
4 Iron Man - Spiderman 54

Diese Tabelle bestätigt unsere vorherige Aussage. Es gibt sehr viele Helden, welche mit allen drei "Haupt-Helden" verbunden sind. Es gibt aber auch sehr viele Helden, welche nur mit einem oder zwei "Haupt-Helden" verbunden sind.

Jeder Haupt-Held hat mehr als 1000 Verbindungen zu anderen einzelnen Helden. Dies zeigt, dass die Haupt-Helden mehr Verbindungen zu einzelnen Helden haben statt mit gemeinsamen Helden. Sie spielen also eine zentrale Rolle im Marvel-Universum.

Gemeinsame Comics von Captain America, Iron Man und Spiderman¶

In [ ]:
# identify names of Captain America, Iron Man und Spider-Man
captain_america_names = [name for name in nodes_df[nodes_df['type'] == 'hero']['node'] if 'CAPTAIN AMERICA' in name]
iron_man_names = [name for name in nodes_df[nodes_df['type'] == 'hero']['node'] if 'IRON MAN' in name]
spider_man_names = [name for name in nodes_df[nodes_df['type'] == 'hero']['node'] if 'SPIDER-MAN' in name]

# find comics with these names
captain_america_comics = edges_df[edges_df['hero'].isin(captain_america_names)]['comic'].unique()
iron_man_comics = edges_df[edges_df['hero'].isin(iron_man_names)]['comic'].unique()
spider_man_comics = edges_df[edges_df['hero'].isin(spider_man_names)]['comic'].unique()

# find common comics
common_comics = set(captain_america_comics) & set(iron_man_comics) & set(spider_man_comics)

# Anzahl und Liste der gemeinsamen Comics
print(f" shared comics: {len(common_comics)} | {common_comics}")
 shared comics: 3 | {'ASM2 1', 'SMTU 4', 'A 400'}

Comic Namen:

  • A 400 : AVENGERS 400
  • SMTU 4 : Spider-Man Team-Up 4
  • ASM2 1 : Amazing Spider-Man 2

In diesen 3 Comics, kommen alle 3 zentralsten Helden vor. Dies ist für uns überaschend, da wir dachten, dass es mehr Comics gibt, in denen alle 3 Helden vorkommen.

Frage 2 - Wie ist die Verteilung der Anzahl der Comics pro Held?¶

Diese Frage haben wir zu Beginn im Meilenstein definiert, aber schon nach der Explorativen Datenanalyse gelöst.

In [ ]:
# Count the number of mentions for each hero
hero_mentions = edges_df['hero'].value_counts()
top_heroes = hero_mentions.head(10)

fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(18, 8))

# Top 10 most frequent comics
sns.barplot(x=top_heroes.values, y=top_heroes.index, ax=axes[0])
axes[0].set_title('Top 10 am häufigsten vorkommende Helden in Comics')
axes[0].set_xlabel('Anzahl der Erwähnungen')
axes[0].set_ylabel('Helden')

# Distribution plot of mentions per comic
sns.histplot(hero_mentions, bins=50, kde=True, ax=axes[1])
axes[1].set_title('Verteilung der Anzahl von Erwähnungen pro Held')
axes[1].set_xlabel('Anzahl der Erwähnungen')
axes[1].set_ylabel('Anzahl der Helden')

plt.tight_layout()
plt.show()

Frage 3 - Gibt es Gruppen oder Gemeinschaften von Helden, die häufig zusammen in Comics erscheinen?¶

Für die Community-Detection verwenden wir die Greedy-Modularity-Methode. Der Algorithmus basiert auf der Maximierung der Modularity über alle möglichen Partitionen des Netzwerks. Modularity ist ein Mass, das die Dichte der Kanten innerhalb von Gemeinschaften im Vergleich zu einer zufälligen Verteilung von Kanten bewertet. Wenn die Modularity eines bestimmten Netzwerks höher ist als die eines zufälligen Netzwerks, deutet dies auf eine strukturierte Gemeinschaftsbildung hin.

Ziel ist es, ein Verständnis über verschiedene Gruppen von Helden zu erhalten, die häufig zusammen in Comics auftreten. Hierzu werden wir die Kantengewichtung hervorheben, um die Stärke der Verbindung zwischen den Helden in einem Community zu visualisieren. Dazu werden wir die Kantengewichtung auf die Dicke der Kanten abbilden. Wir werden auch die Edge-Betweenness bei den gefundenen Communities berechnen, um zu sehen, welche Kanten die wichtigsten sind, um die Gemeinschaften zusammenzuhalten.

Ganzes Netzwerk¶

Gemeinschaften¶

In [ ]:
communities = get_communities(G_one_mode, "Helden")
Gemeinschaft 1: ['8-BALL/', 'A', "A'YIN", 'ABBOTT, JACK', 'ABCISSA', 'ABEL', 'ABSALOM', 'ACHEBE, REVEREND DOC', 'ADAM X', 'ADAMS, CONGRESSMAN H'] ... [2756 Helden]
Gemeinschaft 2: ['3-D MAN/CHARLES CHAN', '4-D MAN/MERCURIO', 'ABOMINATION/EMIL BLO', 'ABOMINATRIX', 'ABSORBING MAN/CARL C', 'ACBA', 'ACHILLES II/HELMUT', 'ACROBAT/CARL ZANTE', 'ADAMS, CINDY', 'ADAMS, GEORGE'] ... [2511 Helden]
Gemeinschaft 3: ['ADAMSON, JASON', 'ADAMSON, REBECCA', 'AENTAROS', 'AGENT 18/JACK TRUMAN', 'ALIST', 'ANDERSON, GINA', 'AUSTIN, GEN.', 'AXIS/', 'AZAZEL', 'BELGRADE, DR. HORATI'] ... [171 Helden]
Gemeinschaft 4: ['ABOMINATION | MUTANT', 'ABSORBING MAN | MUTA', 'ANCIENT ONE/BARON MO', 'APOCALYPSE | MUTANT', 'AURORA | MUTANT X-VE', 'BETA RAY BILL | MUTA', 'BEYONDER | MUTANT X-', 'BINARY | MUTANT X-VE', 'BLACK BOLT | MUTANT', 'BLACK PANTHER | MUTA'] ... [117 Helden]
Gemeinschaft 5: ['AQUARIUS III/', 'ARIES III/', 'ARKANIAN, GLIB', 'BARON ZEBEK', 'BAV-TEK', 'BECKLEY, BENNY', 'BEL-DANN, GEN.', 'BRASS BISHOP/', 'BUG', 'CANCER II/'] ... [69 Helden]

Total number of communities detected: 118

In unserem Netzwerk gibt es total 118 Gemeinschaften. Die grösste Gemeinschaft besteht aus 2'756 Helden, die kleinste aus 1 Held.

Da es recht viele Gemeinschaften gibt, werden wir uns auf 2-3 Gemeinschaften konzentrieren.

Entscheidung der Communities für eine detaillierte Analyse¶

  • Communities 1 und 2 sind sehr gross und man würde den Überblick verlieren. Darum werden wir deise nicht betrachten.
  • Ab Community 3 sind die Communities viel kleiner und so einfacher zu analysieren. Uns ist aufgefallen, dass sich die Communities sehr variieren und wir haben uns für 3, 7 und 14 entschieden.

Community 3¶

Visualisieren wir zuerst die dritte Gemeinschaft, wobei wir die Kantengewichtung hervorheben.

In [ ]:
plot_community(G_one_mode, communities, 2)
Top 10 Kantengewichte der Helden in der Community 3:
BLACK FOX/ROBERT W. - PIXIE/ [ETERNAL]: 76
BLACK FOX/ROBERT W. - EFFIGY/LT. VELMAX/JA: 75
EFFIGY/LT. VELMAX/JA - PIXIE/ [ETERNAL]: 74
BLACK FOX/ROBERT W. - MR. JUSTICE/TIMOTHY: 72
EFFIGY/LT. VELMAX/JA - MR. JUSTICE/TIMOTHY: 72
PIXIE/ [ETERNAL] - MR. JUSTICE/TIMOTHY: 72
LOCKE, DR. CASSANDRA - PIXIE/ [ETERNAL]: 63
EFFIGY/LT. VELMAX/JA - OXBOW/: 63
PIXIE/ [ETERNAL] - OXBOW/: 63
YETI - PIXIE/ [ETERNAL]: 62

Interessanterweise sehen wir in diesem Community weitere Gruppierungen von Helden. Einige sind stark miteinander verbunden, andere weniger. Wir sehen auch, dass einige Helden eine Verbindung zu Helden aus anderen Gemeinschaften haben (z.B. VYREK). Dieser Held dient als "Brücke" zwischen den Sub-Gemeinschaften.

Edge Betweenness¶
In [ ]:
plot_community_edge_betweenness(G_one_mode, communities, 2)
Top 10 Edges by Edge Betweenness Centrality in Community 3:
('MERRYWEATHER, IRENE', 'BLOODSTONE/ULYSSES B'): 0.4479
('UNI-LORD', 'VYREK'): 0.0982
('BLACK FOX/ROBERT W.', 'BLOODSTONE/ULYSSES B'): 0.0971
('EFFIGY/LT. VELMAX/JA', 'BLOODSTONE/ULYSSES B'): 0.0944
('NIGHTINGALE/', 'BLOODSTONE/ULYSSES B'): 0.0731
('VYREK', 'DARK COUNSEL/VARTU'): 0.0712
('BLACK FOX/ROBERT W.', 'VYREK'): 0.0569
('KABALLA', 'BLOODSTONE/ULYSSES B'): 0.0561
('VYREK', 'EFFIGY/LT. VELMAX/JA'): 0.0555
('NIGHTINGALE/', 'VYREK'): 0.0507

Wie beim oberen Plot sehen wir kleinere Gruppierungen im Community. Mittels Edge Betweenness können wir jedoch die Verbindungen zwischen den einzelnen Gruppierungen viel besser sehen. Die dicksten Kanten sind diejenigen, die die einzelnen Gruppierungen zusammenhalten.

die Kante 'MERRYWEATHER, IRENE', 'BLOODSTONE/ULYSSES B' hat bei weitem die höchste Edge Betweenness und wir sehen ihre Wichtigkeit in der Visualisierung, wie sie das Netzwerk zusammenhält.

Community 7¶

In [ ]:
plot_community(G_one_mode, communities, 6)
Top 10 Kantengewichte der Helden in der Community 7:
GATOR, LORD - RAM, SIR: 79
GATOR, LORD - URSULA, LADY: 75
RAM, SIR - URSULA, LADY: 75
GATOR, LORD - TYGER, LORD: 71
RAM, SIR - TYGER, LORD: 71
URSULA, LADY - TYGER, LORD: 67
GATOR, LORD - VERMIN, LADY: 63
RAM, SIR - VERMIN, LADY: 63
TYGER, LORD - VERMIN, LADY: 63
URSULA, LADY - VERMIN, LADY: 59
Edge Betweenness¶
In [ ]:
plot_community_edge_betweenness(G_one_mode, communities, 6)
Top 10 Edges by Edge Betweenness Centrality in Community 7:
('GATOR, LORD', 'STRYKER, WILLIS'): 0.1423
('RAM, SIR', 'STRYKER, WILLIS'): 0.1423
('STRYKER, WILLIS', 'TYGER, LORD'): 0.1423
('LOR, KONA', 'BOLT, COUNCILMAN AND'): 0.1423
('BOLT, COUNCILMAN AND', 'CIR, DYLON'): 0.1423
('MEDINA, ANALISA MARI', 'STRYKER, WILLIS'): 0.1246
('STRYKER, WILLIS', 'ANDREWS, DARYL TROOP'): 0.1108
('POWER MAN III/STEELE', 'STRYKER, WILLIS'): 0.0971
('STRYKER, WILLIS', 'HARDCORE/'): 0.0742
('GATOR, LORD', 'CIR, DYLON'): 0.0601

Einige Charaktere, insbesondere die in der Mitte des Netzwerks, spielen Schlüsselrollen innerhalb ihrer Gemeinschaft, da sie mehrere dickere Kanten haben (höhere Edge-Betweenness) und so die "Sub-Netzwerke" verbinden. Zum Beispiel:

  • 'STRYKER, WILLIS'
  • 'BOLT, COUNCILMAN AND'
  • 'TYGER, LORD'
  • 'GATOR, LORD'

Community 14¶

In [ ]:
plot_community(G_one_mode, communities, 13)
Top 10 Kantengewichte der Helden in der Community 14:
CUMMINGS, DIANE - O'BRYAN, BOB: 20
CUMMINGS, DIANE - IT: 20
O'BRYAN, BOB - IT: 20
CUMMINGS, DIANE - SIMON, FELIX: 19
SIMON, FELIX - O'BRYAN, BOB: 19
SIMON, FELIX - IT: 19
KNUTZ, CINDY - PHIM, FATHER: 14
MUNSON, CLAUDIA - CUMMINGS, DIANE: 13
MUNSON, CLAUDIA - KNUTZ, CINDY: 13
MUNSON, CLAUDIA - SIMON, FELIX: 13

In diesem Netzwerk ist es interessant zu sehen, dass sehr viele Helden eine starke Verbindung miteinander haben. Nur wenige haben eine kleinere Kantengewichtung. Dies deutet darauf hin, dass die Helden in dieser Gemeinschaft in vielen verschiedenen Comics zusammen auftreten. Es gibt keine "Brücken"-Helden, die die Gemeinschaft mit anderen Helden verbinden. Dies ist ein Hinweis darauf, dass die Helden in dieser Gemeinschaft in vielen verschiedenen Comics zusammen auftreten.

Edge Betweenness¶
In [ ]:
plot_community_edge_betweenness(G_one_mode, communities, 13)
Top 10 Edges by Edge Betweenness Centrality in Community 14:
('LEFLER, BOB', 'KNUTZ, CINDY'): 0.0980
('COWAN, EARL', 'KNUTZ, CINDY'): 0.0980
('KNUTZ, CINDY', 'BOSCO, ELAINE'): 0.0980
('CUMMINGS, DIANE', 'KNUTZ, CINDY'): 0.0588
('KNUTZ, CINDY', 'IT'): 0.0588
('KNUTZ, CINDY', 'SIMON, FELIX'): 0.0588
('KNUTZ, CINDY', "O'BRYAN, BOB"): 0.0588
('MUNSON, CLAUDIA', 'KNUTZ, CINDY'): 0.0261
('KNUTZ, CINDY', 'GREER, DAVID'): 0.0261
('KNUTZ, CINDY', 'LOPEZ, MARIA II'): 0.0261

Bei diesem Netzwerk sind die Verbindungen ausgeglichener und es gibt nicht wirklich eine Kante, welche evrschiedene Gruppierungen des Geeinschafts zusammenhält. Ausser die Verbindungen zu 'KNUTZ, CINDY' sind etwas stärker.

Top 100 Helden Netzwerk¶

Wir werden nun untersuchen, welche Gemeinschaften es bei den 100 am häufigsten vorkommenden Helden gibt.

Gemeinschaften¶

In [ ]:
communities_top_100 = get_communities(top_heroes_subgraph, "Helden")

plt.figure(figsize=(20, 15))
plt.title("Community detection for the 100 most popular heroes in the Marvel Universe", fontsize=16)
draw_graph_and_color_groups(top_heroes_subgraph, communities_top_100, "spring")
plt.show()
Gemeinschaft 1: ['ANGEL/WARREN KENNETH', 'ANT-MAN/DR. HENRY J.', 'BALDER [ASGARDIAN]', 'BANNER, BETTY ROSS T', "BLACK PANTHER/T'CHAL", 'CAGE, LUKE/CARL LUCA', 'CAPTAIN MARVEL II/MO', 'CLEA', 'CRYSTAL [INHUMAN]', 'CYCLOPS/SCOTT SUMMER'] ... [55 Helden]
Gemeinschaft 2: ['BANSHEE/SEAN CASSIDY', 'BEAST/HENRY &HANK& P', 'BINARY/CAROL DANVERS', 'BLACK KNIGHT V/DANE', 'BLACK WIDOW/NATASHA', 'BOOMER/TABITHA SMITH', 'CANNONBALL II/SAM GU', 'CAPTAIN AMERICA', 'CAPTAIN BRITAIN/BRIA', 'COLOSSUS II/PETER RA'] ... [45 Helden]

Total number of communities detected: 2

Die Greedy Modularity Methode liefert bei dem Netzwerk mit den top 100 Helden 2 verschiedene Communities. Die Visualisierung zeigt eine hohe Dichte an Verbindungen zwischen den Helden, was die Zusammenarbeit und Interaktionen innerhalb des Marvel-Universums widerspiegelt. Einige Helden scheinen zentrale Knotenpunkte in ihren Gemeinschaften zu sein, mit vielen Verbindungen zu anderen Helden derselben Community.

Community 1 scheint eher rechts zu sein und Community 2 eher links. Die Communities überschneiden sich jedoch stark.

Community 1¶

In [ ]:
plot_community(G_one_mode, communities_top_100, 0)
Top 10 Kantengewichte der Helden in der Community 1:
HUMAN TORCH/JOHNNY S - MR. FANTASTIC/REED R: 1177
SCARLET WITCH/WANDA - IRON MAN/TONY STARK: 1079
SCARLET WITCH/WANDA - VISION: 1038
VISION - IRON MAN/TONY STARK: 958
IRON MAN/TONY STARK - THOR/DR. DONALD BLAK: 919
HAWK - IRON MAN/TONY STARK: 916
ANGEL/WARREN KENNETH - CYCLOPS/SCOTT SUMMER: 914
WOLVERINE/LOGAN - CYCLOPS/SCOTT SUMMER: 901
IRON MAN/TONY STARK - WASP/JANET VAN DYNE: 894
SCARLET WITCH/WANDA - HAWK: 885

Community 2¶

In [ ]:
plot_community(G_one_mode, communities_top_100, 1)
Top 10 Kantengewichte der Helden in der Community 2:
INVISIBLE WOMAN/SUE - THING/BENJAMIN J. GR: 1176
CAPTAIN AMERICA - THING/BENJAMIN J. GR: 969
COLOSSUS II/PETER RA - STORM/ORORO MUNROE S: 910
SHE-HULK/JENNIFER WA - CAPTAIN AMERICA: 883
INVISIBLE WOMAN/SUE - CAPTAIN AMERICA: 882
ICEMAN/ROBERT BOBBY - BEAST/HENRY &HANK& P: 879
WONDER MAN/SIMON WIL - CAPTAIN AMERICA: 873
MARVEL GIRL/JEAN GRE - BEAST/HENRY &HANK& P: 868
CAPTAIN AMERICA - BEAST/HENRY &HANK& P: 858
CAPTAIN AMERICA - HERCULES [GREEK GOD]: 838

Beim ganzen Netzwerk haben wir bei einigen Communities die Edge-Betweenness-Zentralität berechnet. Hier ist dies nicht nötig, da die Communities so stark vernetzt sind. Die Edge-Betweenness-Zentralität wären bei allen Kanten ungefähr gleich und ist daher für die Analyse nicht förderlich.

Was haben wir von der Community-Detection gelernt?¶

  • Existenz von Gemeinschaften: Wir haben festgestellt, dass es klar definierte Gruppen oder Gemeinschaften von Helden gibt, die häufig zusammen in Comics erscheinen. Diese Gemeinschaften können Teams, Allianzen oder andere Gruppierungen von Charakteren repräsentieren, die in der Erzählwelt gemeinsame Ziele oder Feinde haben.

  • Schlüsselcharaktere: Innerhalb der identifizierten Gemeinschaften haben wir durch die Berechnung der Edge Betweenness Centrality wichtige Charaktere identifiziert, die als zentrale Verbindungspunkte dienen. Diese Charaktere spielen oft eine entscheidende Rolle in der Erzählung, da sie verschiedene Handlungsstränge zusammenführen oder als wichtige Mitglieder ihrer Gruppen fungieren.

  • Netzwerkdichte: Die Visualisierung des Netzwerks hat gezeigt, dass einige Gemeinschaften eine sehr dichte Vernetzung aufweisen, was auf enge Beziehungen und regelmässige Interaktionen zwischen den Mitgliedern hinweist.

  • Randfiguren: Ebenso konnten wir beobachten, dass es Charaktere gibt, die weniger zentral sind und am Rand der Gemeinschaften stehen. Diese könnten Nebencharaktere sein, die weniger Einfluss auf die Haupterzählung haben.

Frage 4 - Identifizierung von Helden mit einem Grad von 1¶

Diese Frage geht auf unsere Erkenntnisse in der Explorativen Datenanalyse zurück. Wir haben festgestellt, dass es Helden gibt, die nur in einem einzigen Comic auftreten.

In [ ]:
# Calculate degree
degrees = {hero: B.degree(hero) for hero in B.nodes()}

# Filter out the heroes with only one comic
heroes_with_one_comic = [hero for hero, deg in degrees.items() if deg == 1]

print("Anzahl Helden im gesamten Datensatz, welche nur in einem einzigen Comic erscheinen:", len(heroes_with_one_comic))
print(heroes_with_one_comic[:5])
Anzahl Helden im gesamten Datensatz, welche nur in einem einzigen Comic erscheinen: 3275
['24-HOUR MAN/EMMANUEL', 'ABBOTT, JACK', 'ABOMINATION | MUTANT', 'ABSORBING MAN | MUTA', 'ACBA']
In [ ]:
# Filtern nach Helden mit dem höchsten Grad
top_degree_hero, degree = sorted(degrees.items(), key=lambda x: x[1], reverse=True)[0]
print(top_degree_hero + " hat den höchsten Grad mit " + str(degree) + " Comics.")
SPIDER-MAN/PETER PARKER hat den höchsten Grad mit 1577 Comics.

Frage 5 - Gibt es Gruppen von Comics?¶

Hier gilt das gleiche Prinzip wie bei Frage 3. Wir werden die Community-Detection anwenden, um Gruppen von Comics zu identifizieren, die ähnliche Helden enthalten. Aber wir werden hier verschiedene Algorithmen testen.

In [ ]:
# get top 200 comics with the most heroes
edges_df_subset_top_100 = edges_df.groupby('comic').count().sort_values(by='hero', ascending=False).reset_index()
comics_nodes = edges_df_subset_top_100.comic.tolist()

# create a subset of edges_df with only the top 200 comics
edges_df_subset = edges_df.comic.isin(comics_nodes)
edges_df_subset = edges_df[edges_df_subset]

Um Communities mit Networkx zu finden, sollte man keine bipartiten Netzwerke verwenden. Deshalb haben wir uns entschieden, die Comics als Nodes zu nehmen und die Helden als Edges.

Danach testen wir verschiedene Algorithmen und vergleichen diese, welche die besten Ergebnisse liefern.

In [ ]:
G_uni = nx.Graph()

G_uni.add_nodes_from(comics_nodes, bipartite=0)

hero_to_comics = edges_df_subset.groupby('hero')['comic'].apply(set).to_dict()
# Iterate through each hero and their corresponding set of comics
for hero, comics in hero_to_comics.items():
    # Create edges between all pairs of comics that this hero appears in
    for comic1, comic2 in combinations(comics, 2):
        # If an edge already exists (another hero connects these comics), increment the weight
        if G_uni.has_edge(comic1, comic2):
            G_uni[comic1][comic2]['weight'] += 1
        else:
            # Otherwise, create a new edge with weight 1
            G_uni.add_edge(comic1, comic2, weight=1)

Vergleich von Community-Detection-Algorithmen in NetworkX¶

Greedy Modularity Communities¶

  • Erklärung: Dieser Algorithmus optimiert gierig die Modularität eines Netzwerks. Die Modularität misst die Dichte der Kanten innerhalb von Communities im Vergleich zu Kanten zwischen Communities. Eine höhere Modularität bedeutet eine stärkere Aufteilung des Netzwerks in Communities.
  • Am besten für: Grosse Netzwerke, in denen die Modularität effizient berechnet werden kann. Funktioniert gut, wenn Communities intern dicht verbunden sind.
  • Einschränkungen: Kann kleinere Communities innerhalb grösserer nicht immer erkennen. Der gierige Ansatz findet möglicherweise nicht immer die optimale Modularität.

Label Propagation¶

  • Erklärung: Dieser Algorithmus funktioniert, indem er Labels im Netzwerk verbreitet und Communities auf Basis dieses Labeling-Prozesses bildet. Jeder Knoten übernimmt das Label, das die meisten seiner Nachbarn derzeit haben. Dieser iterative Prozess konvergiert zu einem Zustand, in dem die Labels lokal konsistent sind.
  • Am besten für: Grosse Netzwerke, in denen eine schnelle Ausführung erforderlich ist, und die Community-Struktur nicht stark ausgeprägt ist.
  • Einschränkungen: Ergebnisse können zwischen Durchläufen inkonsistent sein, da sie zu verschiedenen Lösungen konvergieren können, und funktioniert möglicherweise nicht gut bei überlappenden Communities.

Vergleich¶

  • Geschwindigkeit: Label Propagation ist im Allgemeinen schneller und skalierbarer für grosse Netzwerke als der Greedy-Modularity, der rechenintensiv ist.
  • Determinismus: Ausser bei Label Propagation, das bei verschiedenen Durchläufen unterschiedliche Ergebnisse liefern kann, sind die anderen Algorithmen deterministisch.
  • Community-Überlappung: Die meisten Algorithmen gehen davon aus, dass Communities disjunkt sind.
In [ ]:
communities_greedy_modularity = community.greedy_modularity_communities(G_uni)

# Print 10 comics from the first 5 communities
for i, com in enumerate(communities_greedy_modularity[:5]):
    print("Community {}: {}".format(i + 1, list(com)[:10]))
Community 1: ['TB 25', 'CA 378', 'CA 323', 'T2 37', 'NFV.S 4', 'INV 4', 'M/CP 48/3', 'A 132', 'T 208', 'TS 24']
Community 2: ['NW 51', 'HMAG 24', 'HMAG 21', 'DEF 33', 'TTA 74', 'NW 14', 'M/CP 56', 'RH2 5', 'DHAWK 12', 'SUB-M 49']
Community 3: ['IF 5', 'W/C', 'DAZZ 22', 'W2 48', 'NM 69', 'FS 2', 'UX 243', 'XF 55', 'S-W 46', 'W2 6']
Community 4: ['ASM 283', 'S-M 84', 'DD 270', 'M/CP 67/4', 'PPTSS 191', 'DD 127', 'UTSM 22', 'WOSM 102', 'ASM 397', 'S-M 54']
Community 5: ['M/PRV 3', 'M/CP 69/2', 'KZ3 15', 'M/CP 9/2', 'TOD 2', 'TOD2 3', 'SSD 4', 'M/CP 12/2', 'MOKF 112', 'MOKF 35']
In [ ]:
communities_label_propagation = list(community.label_propagation_communities(G_uni))

# Print 10 comics from the first 5 communities
for i, com in enumerate(communities_label_propagation[:5]):
    print("Community {}: {}".format(i + 1, list(com)[:10]))
Community 1: ['TB 25', 'HMAG 24', 'TTA 74', 'T2 37', 'NFV.S 4', 'W2 6', 'S-W 46', 'INV 4', 'SENSM 6', 'M/CP 48/3']
Community 2: ['MOKF 71', 'MOKF 96', 'MOKF 57', 'MOKF 89', 'M/CP 1/3', 'MOKF 40', 'MOKF 112', 'MOKF 86', 'MOKF 17', 'MOKF 35']
Community 3: ['M/PRV 3', 'TOD 11', 'DL 11/4', 'TOD MAG 6/2', 'TOD 2', 'TOD2 3', 'TOD 14', 'TOD 33', 'TOD MAG 4/2', 'TOD2 5']
Community 4: ['BHOOD 1', 'BHOOD 2']
Community 5: ['REMNANTS']

Analyse der Unterschiede zwischen den Algorithmen¶

Zuerst müssen wir sicherstellen, dass bei jedem Algorithmus alle Comics enthalten sind.

In [ ]:
amount = 0
for i in range(0, len(communities_greedy_modularity)):
    amount += len(communities_greedy_modularity[i])
print(f"Amount of comics in communities greedy modularity: {amount}")

amount = 0
for i in range(0, len(communities_label_propagation)):
    amount += len(communities_label_propagation[i])
print(f"Amount of comics in communities louvain: {amount}")
Amount of comics in communities greedy modularity: 12651
Amount of comics in communities louvain: 12651

Alle Algorithmen beinhalten in den jeweiligen Communities alle Comics.

Anzahl der Comunities¶

In [ ]:
print(f"Anzahl der Communities in greedy modularity: {len(communities_greedy_modularity)}")
print(f"Anzahl der Communities in label propagation: {len(communities_label_propagation)}")
Anzahl der Communities in greedy modularity: 33
Anzahl der Communities in label propagation: 40

Hier sehen wir, wie viele Communities jeder Algorithmus gefunden hat. Label Propagation hat mehr gefunden als Greedy Modularity.

In [ ]:
modularity_1 = community.modularity(G_uni, communities_greedy_modularity)
modularity_2 = community.modularity(G_uni, communities_label_propagation)

print(f"Modularity for greedy modularity: {modularity_1}")
print(f"Modularity for label propagation: {modularity_2}")
Modularity for greedy modularity: 0.4259780812171445
Modularity for label propagation: 0.006329685356462341

Schlussfolgerungen zur Modularity¶

Die Modularitätswerte für die verschiedenen Community-Detection-Algorithmen (Greedy Modularity und Label Propagation) liefern wichtige Einblicke in die Effektivität dieser Methoden bei der Identifizierung klar strukturierter Gemeinschaften innerhalb des Netzwerks. Hier sind die detaillierten Schlussfolgerungen:

Greedy Modularity¶

  • Modularität: 0.4259780812171445
  • Interpretation: Dieser positive Wert zeigt an, dass der Algorithmus funktionierende Communities identifizieren konnte. Obwohl die Communities nicht extrem dicht vernetzt sind, gibt es eine erkennbare Struktur, die sich von einer zufälligen Verteilung unterscheidet.

Label Propagation¶

  • Modularität: 0.006329685356462341
  • Interpretation: Dieser Wert ist ziemlich niedrig, was darauf hindeutet, dass die Aufteilung des Netzwerks in Gemeinschaften nicht besonders gut ist. Es bedeutet, dass die Verbindungen zwischen den Knoten im Netzwerk eher zufällig oder chaotisch sind und keine klare Struktur oder starke Trennung zwischen den Gemeinschaften aufweisen.

Gesamteinschätzung¶

Basierend auf den Modularitätswerten scheint der Greedy Modularity-Algorithmus besser geeignet zu sein, um strukturierte Communities in unserem Netzwerk zu identifizieren. Label Propagation zeigt in diesem spezifischen Kontext geringere Effektivität. Es ist wichtig zu beachten, dass die Wirksamkeit von Community-Detection-Algorithmen stark von der Struktur des jeweiligen Netzwerks abhängt. Verschiedene Netzwerke können auf die gleichen Algorithmen unterschiedlich reagieren. In der Regel ist ein höherer Modularity-Wert in der Netzwerkanalyse wünschenswert.

In [ ]:
import matplotlib.pyplot as plt
import networkx as nx

# Convert the community structure to a dictionary for easier access
community_dict_greedy = {node: idx for idx, comm in enumerate(communities_greedy_modularity) for node in comm}
community_dict_label = {node: idx for idx, comm in enumerate(communities_label_propagation) for node in comm}

# Define a coloring function
def coloring_function(community_dict, node):
    return community_dict.get(node, 0)  # Default color if the node is not in any community

pos = nx.spring_layout(G_uni)
colors_greedy = [coloring_function(community_dict_greedy, node) for node in G_uni.nodes()]
colors_label = [coloring_function(community_dict_label, node) for node in G_uni.nodes()]

# Create a subplot for each set of community colors
fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(20, 20))

# Greedy Modularity Communities Plot
nx.draw_networkx(G_uni, pos, node_color=colors_greedy, ax=axes[0])
axes[0].set_title('Greedy Modularity Communities')

# Label Propagation Algorithm Plot
nx.draw_networkx(G_uni, pos, node_color=colors_label, ax=axes[1])
axes[1].set_title('Label Propagation Communities')

plt.tight_layout()
plt.show()
  • Greedy Modularity Communities: In diesem Plot scheint es eine grosse zentrale Gemeinschaft zu geben, die durch eine massive Ansammlung von Knoten dargestellt wird, was auf eine Gruppe von eng verbundenen Elementen hindeutet. Einige kleinere, klar getrennte Gruppen sind am Rand sichtbar, was auf spezialisierte Cluster hindeutet, die möglicherweise einzigartige oder weniger verbreitete Themen repräsentieren.

  • Label Propagation Communities: Die Ergebnisse scheinen ähnlich zur Greedy Modularity zu sein, mit einer grossen zentralen Gemeinschaft und einigen isolierten Gruppen. Dies deutet darauf hin, dass auch der Label Propagation Algorithmus eine starke Hauptgruppe innerhalb des Netzwerks identifiziert hat, zusätzlich zu einigen distinkten kleineren Gemeinschaften.

In beiden Fällen zeigt die Präsenz einer dominierenden zentralen Gemeinschaft, dass es eine Gruppe von stark interagierenden Elementen gibt, die eine Kernstruktur innerhalb des Netzwerks bilden. Die kleineren und isolierten Gemeinschaften könnten Nischengruppen oder Subgruppen darstellen, die spezifische oder separate Handlungsstränge repräsentieren.

In [ ]:
community_sizes_greedy_modularity = [len(c) for c in communities_greedy_modularity]
print(f"Sizes of communities: {community_sizes_greedy_modularity}")

community_sizes_label_propagation = [len(c) for c in communities_label_propagation]
print(f"Sizes of communities: {community_sizes_label_propagation}")
Sizes of communities: [4159, 3425, 2393, 2046, 526, 19, 14, 12, 10, 9, 9, 4, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
Sizes of communities: [12178, 142, 132, 2, 1, 3, 2, 14, 11, 9, 6, 19, 9, 2, 4, 1, 13, 1, 22, 1, 1, 10, 14, 1, 1, 1, 1, 6, 1, 1, 5, 1, 1, 2, 1, 1, 27, 1, 1, 2]

Frage 6 - Welchen Einfluss auf das Netzwerk haben die Helden, welche am meisten im vorkommen?¶

Wir wollen nun untersuchen, wie das Netzwerk aussieht, wenn wir die bekanntesten Helden entfernen. Hier nochmals die top 10 Helden sortiert nach Degree Centrality aus dem ganzen Netzwerk:

In [ ]:
top_heroes_by_degree = [hero for hero, _ in sorted(degree_centrality.items(), key=lambda item: item[1], reverse=True)[:10]]
top_heroes_by_degree
Out[ ]:
['CAPTAIN AMERICA',
 'SPIDER-MAN/PETER PARKER',
 'IRON MAN/TONY STARK',
 'THING/BENJAMIN J. GR',
 'MR. FANTASTIC/REED R',
 'HUMAN TORCH/JOHNNY S',
 'WOLVERINE/LOGAN',
 'SCARLET WITCH/WANDA',
 'BEAST/HENRY &HANK& P',
 'THOR/DR. DONALD BLAK']
In [ ]:
def calculate_metrics(G):
    """
    Calculate various graph metrics.
    """
    metrics = {
        'number_of_edges': G.number_of_edges(),
        'connected_components': nx.number_connected_components(G),
        'average_clustering_coefficient': nx.average_clustering(G),
        'network_density': nx.density(G)
    }
    return metrics

# DataFrame to store the metrics
metrics_df = pd.DataFrame(columns = [
    'Hero Removed', 
    'Number of Edges', 
    'Connected Components', 
    'Average Clustering Coefficient', 
    'Network Density'])

# For each hero in the top 10, remove the hero and calculate metrics
for hero in top_heroes_by_degree:
    G_temp = G_one_mode.copy()
    G_temp.remove_node(hero)
    updated_metrics = calculate_metrics(G_temp)
    metrics_df.loc[len(metrics_df)] = [
        hero, 
        updated_metrics['number_of_edges'], 
        updated_metrics['connected_components'], 
        updated_metrics['average_clustering_coefficient'], 
        updated_metrics['network_density']]
In [ ]:
fig, axs = plt.subplots(2, 2, figsize=(15, 15))

# Plot for Number of Edges
axs[0, 0].plot(metrics_df['Hero Removed'], metrics_df['Number of Edges'], marker='o', color='blue')
axs[0, 0].set_title('Number of edges after removing each hero')
axs[0, 0].set_xlabel('Hero Removed')
axs[0, 0].set_ylabel('Number of Edges')
axs[0, 0].tick_params(axis='x', rotation=90)

# Plot for Connected Components
axs[0, 1].plot(metrics_df['Hero Removed'], metrics_df['Connected Components'], marker='o', color='green')
axs[0, 1].set_title('Connected components after removing each hero')
axs[0, 1].set_xlabel('Hero Removed')
axs[0, 1].set_ylabel('Connected Components')
axs[0, 1].tick_params(axis='x', rotation=90)

# Plot for Average Clustering Coefficient
axs[1, 0].plot(metrics_df['Hero Removed'], metrics_df['Average Clustering Coefficient'], marker='o', color='red')
axs[1, 0].set_title('Average clustering coefficient after removing each hero')
axs[1, 0].set_xlabel('Hero Removed')
axs[1, 0].set_ylabel('Average Clustering Coefficient')
axs[1, 0].tick_params(axis='x', rotation=90)

# Plot for Network Density
axs[1, 1].plot(metrics_df['Hero Removed'], metrics_df['Network Density'], marker='o', color='purple')
axs[1, 1].set_title('Network density after removing each hero')
axs[1, 1].set_xlabel('Hero Removed')
axs[1, 1].set_ylabel('Network Density')
axs[1, 1].tick_params(axis='x', rotation=90)

plt.tight_layout()
plt.show()

Interpretation¶

Anzahl Edges nach dem Entfernen jedes Helden:

Die Grafik zeigt einen leichten Anstieg der Anzahl der Kanten, nachdem bestimmte Helden entfernt wurden. Es macht Sinn, da die Helden mit den höchsten Grad-Zentralitäten diejenigen sind, die die meisten Verbindungen zu anderen Helden haben. Wenn wir diese Helden entfernen, werden die Verbindungen zwischen den anderen Helden sichtbarer.

Verbundene Komponenten nach dem Entfernen jedes Helden:

Es gibt eine auffällige Spitze bei "WOLVERINE/LOGAN", der darauf hinweist, dass die Entfernung dieses Helden einen signifikanten Anstieg der Anzahl der verbundenen Komponenten verursacht. Dies bedeutet, dass der Held als Brücke fungiert, die verschiedene Teile des Netzwerks verbindet. Ohne diesen Helden zersplittert das Netzwerk in mehr unverbundene Teile, was auf die entscheidende Rolle des Helden für den Zusammenhalt des Netzwerks hinweist.

Durchschnittlicher Clustering-Koeffizient nach Entfernung der einzelnen Helden:

Der Clustering-Koeffizient schwankt mit der Entfernung der einzelnen Helden. Einige Helden haben einen grösseren Einfluss auf das lokale Clustering, während andere weniger Einfluss haben. Ein Rückgang des Koeffizienten deutet darauf hin, dass der entfernte Held Teil vieler Dreiecke war und zu einem höheren lokalen Clustering vor der Entfernung beigetragen hat (Captain America, Spider Man, Iron Man und Wolverine). Variationen in dieser Metrik deuten darauf hin, dass einige Helden eine wichtigere Rolle bei der Bildung von engmaschigen Gemeinschaften innerhalb des Netzwerks spielen.

Dichte des Netzwerks nach dem Entfernen jedes Helden:

Die Dichte ist als das Verhältnis von tatsächlichen Kanten zu möglichen Kanten definiert. Wir sehen eine sehr ähnliche Verteilung der Dichte des Netzwerks wie die Anzahl Edges. Die Dichte des Netzwerks nimmt mit der Entfernung der einzelnen Helden zu. Dies macht Sinn, da wenn die zentralsten Helden entfernt werden, die Anzahl der Kanten im Netzwerk abnimmt und die Dichte abnimmt.

Schlussfolgerungen:¶

  • Einflussreiche Helden: Diejenigen Helden, deren Entfernung zu einem sprunghaften Anstieg der verbundenen Komponenten führt, sind wahrscheinlich sehr einflussreich innerhalb des Netzwerks. Ihre Präsenz trägt wesentlich zur Kohäsion des Netzwerks bei, da sie als zentrale Knotenpunkte fungieren, die verschiedene Gemeinschaften oder Gruppen innerhalb der Marvel-Welt miteinander verbinden.

  • Netzwerkresilienz: Die Anzahl der verbundenen Komponenten bleibt bei der Entfernung vieler Helden relativ stabil, was auf eine robuste Netzwerkstruktur hindeutet. Dies könnte bedeuten, dass das Netzwerk über mehrere Schlüsselfiguren oder redundante Verbindungen verfügt, die eine starke Vernetzung auch in Abwesenheit einiger zentraler Charaktere aufrechterhalten.

  • Dichte und Vernetzung: Die Zunahme der Netzwerkdichte nach dem Entfernen der Helden deutet darauf hin, dass das Netzwerk in Bezug auf die verbleibenden Verbindungen dichter wird. Dies legt nahe, dass die entfernten Helden zwar viele Verbindungen hatten, diese aber nicht entscheidend zur allgemeinen Dichte des Netzwerks beigetragen haben.

  • Clustering und Gemeinschaftsbildung: Die Veränderungen in der durchschnittlichen Clustering-Koeffizienten deuten darauf hin, dass manche Helden wesentlich zur Bildung von eng vernetzten Gemeinschaften beitragen. Ihre Entfernung kann die lokale Clusterbildung schwächen, was auf ihre Rolle in der Schaffung oder Aufrechterhaltung von Gruppen innerhalb des Netzwerks hinweist.

Frage 7 - Sind ähnliche Comics Fortsetzungen?¶

Da es sich als sehr aufwändig herausgestellt hat, Fortsetzungen von Comics zu finden, haben wir uns entschieden zwei Methoden zu vergleichen. Wir vergleichen diese Methoden in dem wir vier Serien auswählen und die beiden Methoden (Jaccard und Pfadlänge) vergleichen. Um Fortsetzungen zu finden, wäre es praktisch weitere Informationen zu den Comics zu besitzen. Man könnte dazu auch Machine Learning algorithmus verwenden. Da dies jedoch nicht Teil der Aufgabe ist, haben wir uns entschieden, dies nicht zu machen.

In [ ]:
edges_df_copy = edges_df_subset.copy()

edges_df_copy = edges_df_copy.drop(columns=['hero'])

edges_df_copy['similarity'] = None

Idetifizieren von Serien in unserem Subset um diese zu vergleichen¶

  • Serie 1: Uncanny X-Men
  • Serie 2: Avengers
  • Serie 3: Fantastic Four
  • Serie 4: Contest of Champions
In [ ]:
print(f"Amount of Uncanny X-Men: {len(edges_df_copy[edges_df_copy['comic'].str.contains('UX')].comic.unique())} \n")
amount_of_uncanny_xmen = len(edges_df_copy[edges_df_copy['comic'].str.contains('UX')].comic.unique())
list_of_uncanny_xmen = edges_df_copy[edges_df_copy['comic'].str.contains('UX')].comic.unique()

print(f"Amount of Avengers: {len(edges_df_copy[edges_df_copy['comic'].str.contains('^A ')].comic.unique())} \n")
amounf_of_avengers = len(edges_df_copy[edges_df_copy['comic'].str.contains('^A ')].comic.unique())
list_of_avenger = edges_df_copy[edges_df_copy['comic'].str.contains('^A ')].comic.unique()

print(f"Amount of Fantastic Four: {len(edges_df_copy[edges_df_copy['comic'].str.contains('FF ')].comic.unique())} \n")
amount_of_fantastic_four = len(edges_df_copy[edges_df_copy['comic'].str.contains('FF ')].comic.unique())
list_of_fantastic_four = edges_df_copy[edges_df_copy['comic'].str.contains('FF ')].comic.unique()

print(f"Amount of Contest of Champions: {len(edges_df_copy[edges_df_copy['comic'].str.contains('COC ')].comic.unique())} \n")
amount_of_coc = len(edges_df_copy[edges_df_copy['comic'].str.contains('COC')].comic.unique())
list_of_coc = edges_df_copy[edges_df_copy['comic'].str.contains('COC')].comic.unique()

list_of_comics = ["UX ", "^A ", "FF ", "COC "]
Amount of Uncanny X-Men: 399 

Amount of Avengers: 420 

Amount of Fantastic Four: 438 

Amount of Contest of Champions: 3 

Gibt die Jaccard Similarity darauf aufschluss?¶

In [ ]:
# Create a pivot table
pivot_table = pd.pivot_table(edges_df_subset, index='comic', columns='hero', aggfunc=len, fill_value=0)

# Convert the pivot table to a sparse matrix
sparse_matrix = sparse.csr_matrix(pivot_table.values)

# Calculate the Jaccard distance
jaccard_distances = pdist(sparse_matrix.toarray(), metric='jaccard')

# Convert the distances to a square matrix form and then to similarity
jaccard_similarity = 1 - squareform(jaccard_distances)

# Create a DataFrame for the Jaccard similarity
jaccard_sim_df = pd.DataFrame(jaccard_similarity, index=pivot_table.index, columns=pivot_table.index)

# Replace all 1.00000 with 0.00000
jaccard_sim_df = jaccard_sim_df.replace(1.00000, 0.00000)
In [ ]:
# Change all value from 0 to np.nan
jaccard_sim_df.replace(0, np.nan, inplace=True)

# Loop through each row in edges_df_copy
for i, row in edges_df_copy.iterrows():
    # Calculate the mean cosine similarity for the current index
    mean_similarity = jaccard_sim_df[jaccard_sim_df.index == row['comic']].mean(axis=1).values[0]
    
    # Assign this value to the 'similarity' column of edges_df_copy
    edges_df_copy.at[i, 'similarity'] = mean_similarity

edges_df_copy.drop_duplicates(inplace=True)

X-Men¶

In [ ]:
# Regex pattern to search for "UX "
regex_pattern = r'UX\s'
list_of_percentages = []

# Iterate through the list and print top comics with "UX " in their name
for i in list_of_uncanny_xmen:
    sorted_comics = jaccard_sim_df[i].sort_values(ascending=False).head(amount_of_uncanny_xmen)
    amount_of_comics = 0
    for comic, similarity in sorted_comics.items():
        if re.search(regex_pattern, comic):
            amount_of_comics += 1
    list_of_percentages.append(amount_of_comics/amount_of_uncanny_xmen * 100)

print(f"Mean Percentage: {sum(list_of_percentages)/len(list_of_percentages)}")
Mean Percentage: 38.4137034315112

Anhand der Jaccard Similarity wurden knapp 40% der Fortsetzungen gefunden. Dies ist ein guter Wert, da es sich um eine sehr grosse Serie handelt mit 399 Comics.

Avengers¶

In [ ]:
# Regex pattern to search for "A "
regex_pattern = r'^A\s'
list_of_percentages = []

# Iterate through the list and print top comics with "A " in their name
for i in list_of_avenger:
    sorted_comics = jaccard_sim_df[i].sort_values(ascending=False).head(amounf_of_avengers)
    amount_of_comics = 0
    for comic, similarity in sorted_comics.items():
        if re.search(regex_pattern, comic):
            amount_of_comics += 1
    list_of_percentages.append(amount_of_comics/amounf_of_avengers * 100)
    
print(f"Mean Percentage: {sum(list_of_percentages)/len(list_of_percentages)}")
Mean Percentage: 42.52551020408165

Bei den Avengers findet man etwas mehr Fortsetzungen anhand der Jaccard Similarity. Dies führt zu einem Durchschnittswert von knapp 43%. Diese Methode funktioniert somit bei den Avengers akzeptabel.

Fantastic Four¶

In [ ]:
# Regex pattern to search for "FF "
regex_pattern = r'FF\s'
list_of_percentages = []

# Iterate through the list and print top comics with "FF " in their name
for i in list_of_fantastic_four:
    sorted_comics = jaccard_sim_df[i].sort_values(ascending=False).head(amount_of_fantastic_four)
    amount_of_comics = 0
    for comic, similarity in sorted_comics.items():
        if re.search(regex_pattern, comic):
            amount_of_comics += 1
    list_of_percentages.append(amount_of_comics/amount_of_fantastic_four * 100)

print(f"Mean Percentage: {sum(list_of_percentages)/len(list_of_percentages)}")
Mean Percentage: 58.39275661474951

Bei den Fantastic Four findet man anhand der Similarity die meisten Fortsetzungen. Dies führt zu einem Durchschnittswert von knapp 58%. Diese Methode funktioniert somit bei den Fantastic Four gut.

Contest of Champions¶

In [ ]:
# Regex pattern to search for "COC "
regex_pattern = r'COC\s'
list_of_percentages = []

# Iterate through the list and print top comics with "COC " in their name
for i in list_of_coc:
    sorted_comics = jaccard_sim_df[i].sort_values(ascending=False).head(amount_of_coc)
    amount_of_comics = 0
    for comic, similarity in sorted_comics.items():
        if re.search(regex_pattern, comic):
            amount_of_comics += 1
    list_of_percentages.append(amount_of_comics/amount_of_coc * 100)

print(f"Mean Percentage: {sum(list_of_percentages)/len(list_of_percentages)}")
Mean Percentage: 22.222222222222218

Bei der Serie mit 3 Folgen war die Jaccard Similarity nicht sehr hilfreich. Dies könnte sein, weil diese Serie viele verschiedene Helden vorstellt und somit die Ähnlichkeit zu anderen Serien nicht sehr gross ist. Dies führt zu einem Durchschnittswert von knapp 22%. Diese Methode funktioniert somit bei Contest of Champions nicht gut.

Gibt die Pfadlänge darauf aufschluss?¶

In [ ]:
G_path = nx.Graph()

# Add an edge between comics that share the same hero
for hero, comics in edges_df_subset.groupby('hero')['comic']:
    for comic1, comic2 in combinations(comics, 2):
        G_path.add_edge(comic1, comic2)

# Now calculate the shortest path lengths between all pairs of comics
path_lengths = dict(nx.all_pairs_shortest_path_length(G_path))

# Removing entries with a value of 0
for key, inner_dict in path_lengths.items():
    path_lengths[key] = {k: v for k, v in inner_dict.items() if v != 0}
In [ ]:
pattern_list = ['^A\s', 'FF\s', 'UX\s', 'COC\s']
sorted_path_lengths = {}
amount_dict = {}

# search in dict for string FF in keys
for key in path_lengths.keys():
    for regex_pattern in pattern_list:
        if re.search(regex_pattern, key):
            if regex_pattern == '^A\s':
                sorted_path_lengths[key] = dict(sorted(path_lengths[key].items(), key=lambda item: item[1])[:amounf_of_avengers])
            if regex_pattern == 'FF\s':
                sorted_path_lengths[key] = dict(sorted(path_lengths[key].items(), key=lambda item: item[1])[:amount_of_fantastic_four])
            if regex_pattern == 'UX\s':
                sorted_path_lengths[key] = dict(sorted(path_lengths[key].items(), key=lambda item: item[1])[:amount_of_uncanny_xmen])
            if regex_pattern == 'COC\s':
                sorted_path_lengths[key] = dict(sorted(path_lengths[key].items(), key=lambda item: item[1])[:amount_of_coc])
            amount_of_comics = 0
            for i in sorted_path_lengths[key]:
                if re.search(regex_pattern, i):
                    amount_of_comics += 1
                amount_dict[key] = (amount_of_comics)/(len(sorted_path_lengths[key])) * 100
In [ ]:
for regex in pattern_list:
    avengers_percentage = 0
    amount = 0
    if regex == '^A\s':
        for i in amount_dict.keys():
            if re.search(regex, i):
                avengers_percentage += amount_dict[i]
                amount += 100
        print(f"Percentage of Avengers: {avengers_percentage/amount * 100}")

    if regex == 'FF\s':
        for i in amount_dict.keys():
            if re.search(regex, i):
                avengers_percentage += amount_dict[i]
                amount += 100
        print(f"Percentage of Fantastic Four: {avengers_percentage/amount * 100}")

    if regex == 'UX\s':
        for i in amount_dict.keys():
            if re.search(regex, i):
                avengers_percentage += amount_dict[i]
                amount += 100
        print(f"Percentage of Uncanny X-Men: {avengers_percentage/amount * 100}")

    if regex == 'COC\s':
        for i in amount_dict.keys():
            if re.search(regex, i):
                avengers_percentage += amount_dict[i]
                amount += 100
        print(f"Percentage of Contest of Champion: {avengers_percentage/amount * 100}")
Percentage of Avengers: 44.66496598639466
Percentage of Fantastic Four: 40.65386459831953
Percentage of Uncanny X-Men: 35.2272368578877
Percentage of Contest of Champion: 22.222222222222218

Interpretation & Vergleich¶

Wenn wir die Resultate der Pfadlänge mit der Jaccard Similarity vergleichen, sehen wir, dass die Pfadlänge nicht sehr gut funktioniert. Die Pfadlänge liefert etwas schwächere Resultate als der Jaccard Similarity (ausser bei den Avengers ist die Pfadlänge um 2% höher und bei COC gleich).

Das Fazit ist aus diesem Versuch, dass sich Jaccard Similarity besser eignet um Fortsetzungen zu finden. Jedoch ist es auch mit dieser nicht sehr vielversprechend, da nicht einmal die Hälfte der Fortsetzungen gefunden wurden. Man müsste mehr Attribute der Comics kennen um bessere Resultate zu erzielen und verschiedene Methoden miteinander kombinieren um bessere Resultate zu erzielen. Wenn wir hier genauere Analysen machen würden, hätten diese nicht mehr viel mit dem Thema Soziale Netzwerke analysieren zu tun, sondern eher mit Machine Learning.

Verteilung der Ähnlichkeiten¶

In [ ]:
# Initialize an empty dictionary
non_nan_columns_per_row = {}

# Iterate over each row in the DataFrame
for index, row in jaccard_sim_df.iterrows():
    # Initialize a list for this row index
    non_nan_columns_per_row[index] = []
    # For each row, iterate over each column
    for col in jaccard_sim_df.columns:
        # Check if the value in the column for this row is not NaN and greater than 0.5
        if not pd.isna(row[col]) and row[col] > 0.5 and col != index:
            # Append the column name to the list for this row index
            non_nan_columns_per_row[index].append(col)
In [ ]:
# sort the dictionary by the length of the list of similar comics
sorted_non_nan_columns_per_row = {k: v for k, v in sorted(non_nan_columns_per_row.items(), key=lambda item: len(item[1]), reverse=True)}

# plot the lengths of the lists
plt.figure(figsize=(15, 8))
sns.histplot(data=[len(v) for k, v in sorted_non_nan_columns_per_row.items()], bins=100)
plt.title('Distribution of number of similar comics')
plt.xlabel('Number of similar comics')
plt.ylabel('Number of comics')
plt.show()

print("Comics mit den meisten ähnlichen Comics:")
print(list(sorted_non_nan_columns_per_row.keys())[:10])
Comics mit den meisten ähnlichen Comics:
['DH 9', 'FF 203', 'MICRO 40', 'TG 23', 'FF 106', 'FF 124', 'FF 201', 'FF 221', 'FF2 11', 'FF2 7']

Die meisten Comics haben bis zu 10 ähnliche Comics / Fortsetzungen. Es gibt jedoch einige Ausreisser, die bis zu etwa 170 ähnliche Comics haben. Diese Comics gehören zu den am stärksten vernetzten Comics im Netzwerk. Die "Fantastic Four" Comics kommen bei diesen Ausreisser oft vor. Dies macht Sinn, da in diesen Comics viele gleiche Helden vorkommen.

Frage 8 Welche Helden könnten in einem Comic zusammen auftreten?¶

Um diese Frage zu beantworten, werden wir Link Prediction und Common Neighbors verwenden.

In [ ]:
import itertools

def predict_links(G, top_n=10):
    """
    Predict future links using the number of common neighbors.
    
    Parameters:
    G (nx.Graph): The graph on which to perform link prediction.
    top_n (int): The number of link predictions to return.
    
    Returns:
    list of tuples: A list of the top predicted links and their scores.
    """
    # Calculate all possible non-existent edges
    potential_links = [(node1, node2) for node1, node2 in itertools.combinations(G.nodes(), 2) if not G.has_edge(node1, node2)]
    
    # Get the number of common neighbors between each pair of nodes
    scores = [(node1, node2, len(list(nx.common_neighbors(G, node1, node2)))) for node1, node2 in potential_links]
    
    # Sort the potential links
    scores.sort(key=lambda x: x[2], reverse=True)
    
    return scores[:top_n]

predicted_links = predict_links(G_one_mode, top_n=10)
for link in predicted_links:
    print(f"{link[0]} - {link[1]}: {link[2]} gemeinsame Nachbarn")
CAPTAIN MARVEL II/MO - ICEMAN/ROBERT BOBBY: 288 gemeinsame Nachbarn
HAVOK/ALEX SUMMERS - JARVIS, EDWIN: 265 gemeinsame Nachbarn
JAMESON, J. JONAH - SHADOWCAT/KATHERINE: 250 gemeinsame Nachbarn
BLOB/FRED J. DUKES - THING/BENJAMIN J. GR: 243 gemeinsame Nachbarn
BLOB/FRED J. DUKES - HUMAN TORCH/JOHNNY S: 237 gemeinsame Nachbarn
BANSHEE/SEAN CASSIDY - IRON MAN/TONY STARK: 236 gemeinsame Nachbarn
BLOB/FRED J. DUKES - INVISIBLE WOMAN/SUE: 236 gemeinsame Nachbarn
CAPTAIN BRITAIN/BRIA - JARVIS, EDWIN: 233 gemeinsame Nachbarn
RICHARDS, FRANKLIN B - SERSI/SYLVIA: 233 gemeinsame Nachbarn
MARVEL GIRL/JEAN GRE - TIGRA/GREER NELSON: 232 gemeinsame Nachbarn

Aufgelistet sind Paare von Marvel-Helden, welche keine direkte Verbindung zu einander haben. Die Helden sind nach der Anzahl gemeinsame Nachbarn sortiert. Je mehr, desto wahrscheinlicher ist es, dass sie in einem Comic zusammen auftreten könnten.

Dies ist eventuell für die Marvel-Publisher interessant, da sie so neue Ideen für Comics erhalten könnten. Neue Verbindungen könnten hergestellt werden, welche vorher noch nicht existierten und so könnten etwas unbekanntere Helden promoted werden.

Ausblick¶

Für die Zukunft dieses Projekts zur Analyse des Marvel-Sozialnetzwerks gibt es mehrere spannende Erweiterungsmöglichkeiten, die zu einem tieferen Verständnis des Universums und seiner Charakterdynamik führen könnten:

Erweiterung der Datengrundlage¶

Die Integration von Informationen aus verschiedenen Medienformaten wie Filmen, TV-Serien und Videospielen kann zu einem gesamtheitlichen Bild des Marvel-Universums beitragen. Dazu gehören nicht nur die Beziehungen zwischen den Helden, sondern auch die zu ihren Gegenspielern. Ein umfassendes Bild der Feindschaften, Allianzen und Konflikte könnte aufzeigen, wie Charakterbeziehungen das narrative Gefüge prägen.

Tiefere Einblicke in Charakterinteraktionen¶

Durch die Analyse der Feinde der Helden und der Konflikte zwischen ihnen könnte man verstehen, welche Charaktere die Hauptantagonisten innerhalb des Universums sind. Es wäre interessant zu erforschen, ob und wie die Präsenz von Feinden die Verbindungen zwischen Helden beeinflusst, sowie die Dynamik von Helden, die sich gegenseitig bekämpfen.

Historische Analyse (Jahreszahlen)¶

Eine zeitliche Analyse der Charakterentwicklungen und ihrer Interaktionen könnte Trends und Muster in der Erzählentwicklung offenlegen. Dabei könnten wichtige Ereignisse wie der Tod eines Helden oder signifikante Wendepunkte in der Storyline Berücksichtigung finden.

Einsatz von Web-Crawlern und APIs¶

Ein systematischer Einsatz von Web-Crawlern, insbesondere die Nutzung der Marvel API, kann dabei helfen, die Datenbanken kontinuierlich zu aktualisieren und zu erweitern. Automatisierte Skripte könnten neue Veröffentlichungen und Informationen in Echtzeit erfassen, was die Analyse aktuell und relevant hält.

Optimierung der Berechnungsmethoden¶

Effiziente Algorithmen und Rechentechniken sind nötig, um mit der wachsenden Datenmenge umzugehen. Hier könnten beispielsweise maschinelles Lernen und Netzwerk-Sampling-Techniken zum Einsatz kommen, um die Berechnung von Metriken und Vorhersagen zu beschleunigen.

Lessons Learned¶

Pro:​¶

  • Spannendes Projekt​
  • NetworkX​
  • Communtiy Detection​: Existenz von Gemeinschaften, Schlüsselcharaktere, Netzwerkdichte

Contra:​¶

  • Datenreduzierung​
  • Netzwerk mit mehr Attribute (Marvel API)​
  • Öfters nach Feedback fragen